// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#include <pollux/type/huge_int.h>

namespace kumo::pollux {
    int128_t HugeInt::parse(const std::string &str) {
        int128_t result = 0;
        bool negative = false;
        size_t idx = 0;

        POLLUX_CHECK(!str.empty(), "Empty string cannot be converted to int128_t.");

        for (; idx < str.length() && str.at(idx) == ' '; ++idx) {
        }

        if (idx < str.length() && str.at(idx) == '+') {
            ++idx;
        } else if (idx < str.length() && str.at(idx) == '-') {
            ++idx;
            negative = true;
        }

        int128_t max = std::numeric_limits<int128_t>::max();
        int128_t min = std::numeric_limits<int128_t>::min();
        for (; idx < str.size(); ++idx) {
            POLLUX_CHECK(
                std::isdigit(str[idx]),
                "Invalid character {} in the string.",
                str[idx]);

            // Throw error if the result is out of the range of int128_t, and return the
            // result before computing the last digit if the digit string would be the
            // min or max value of int128_t to avoid the potential overflow issue making
            // it more robust.
            int128_t cur = str[idx] - '0';
            if ((result > max / 10)) {
                POLLUX_FAIL(fmt::format("{} is out of range of int128_t", str));
            }

            int128_t num = cur - (max % 10);
            if (result == (max / 10)) {
                if (negative) {
                    if (num > 1) {
                        POLLUX_FAIL(fmt::format("{} is out of range of int128_t", str));
                    } else if (num == 1) {
                        return min;
                    }
                } else {
                    if (num > 0) {
                        POLLUX_FAIL(fmt::format("{} is out of range of int128_t", str));
                    } else if (num == 0) {
                        return max;
                    }
                }
            }

            result = result * 10 + cur;
        }

        return negative ? -result : result;
    }
} // namespace kumo::pollux

namespace std {
    string to_string(kumo::pollux::int128_t x) {
        if (x == 0) {
            return "0";
        }
        string ans;
        bool negative = x < 0;
        while (x != 0) {
            ans += '0' + abs(static_cast<int>(x % 10));
            x /= 10;
        }
        if (negative) {
            ans += '-';
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
} // namespace std
